import XCTest
import Stubber

class StubberTests: XCTestCase {
  override func setUp() {
    super.setUp()
    Stubber.clear()
  }

  func test_argument0_defaultNo_returnVoid() {
    // given
    let f = StubClass().argument0_returnVoid_defaultNo
    Stubber.register(f) { _ in }

    // when
    f()

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
  }

  func test_argument0_defaultVoid_returnVoid() {
    // given
    let f = StubClass().argument0_returnVoid_defaultVoid

    // when
    f()
    f()

    // then
    XCTAssertEqual(Stubber.executions(f).count, 2)
  }

  func test_argument1_defaultNo_returnInt() {
    // given
    let f = StubClass().argument1_returnInt_defaultNo
    Stubber.register(f) { _ in 10 }

    // when
    let result = f("Hello")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments, "Hello")
    XCTAssertEqual(Stubber.executions(f)[0].result, 10)
    XCTAssertEqual(result, 10)
  }

  func test_argument1_defaultInt_returnInt() {
    // given
    let f = StubClass().argument1_returnInt_defaultInt

    // when
    let result = f("Hello, default!")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments, "Hello, default!")
    XCTAssertEqual(Stubber.executions(f)[0].result, 0)
    XCTAssertEqual(result, 0)
  }

  func test_argument1_parameterOptional_returnBool_defaultNo() {
    // given
    let f = StubClass().argument1_parameterOptional_returnBool_defaultNo
    Stubber.register(f) { key in
      return true
    }

    // when
    let result = f("String Parameter")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments, "String Parameter")
    XCTAssertEqual(Stubber.executions(f)[0].result, true)
    XCTAssertEqual(result, true)
  }

  func test_argument1_parameterOptional_returnBool_defaultBool() {
    // given
    let f = StubClass().argument1_parameterOptional_returnBool_defaultBool

    // when
    let result = f("String Value")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments, "String Value")
    XCTAssertEqual(Stubber.executions(f)[0].result, false)
    XCTAssertEqual(result, false)
  }

  func test_argument2_parameterOptional_returnBool_defaultNo() {
    // given
    let f = StubClass().argument2_parameterOptional_returnBool_defaultNo
    Stubber.register(f) { key in
      return true
    }

    // when
    let result = f("String1", "String2")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "String1")
    XCTAssertEqual(Stubber.executions(f)[0].arguments.1, "String2")
    XCTAssertEqual(Stubber.executions(f)[0].result, true)
    XCTAssertEqual(result, true)
  }

  func test_argument2_parameterOptional_returnBool_defaultBool() {
    // given
    let f = StubClass().argument2_parameterOptional_returnBool_defaultBool

    // when
    let result = f("String Value1", "String Value2")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "String Value1")
    XCTAssertEqual(Stubber.executions(f)[0].arguments.1, "String Value2")
    XCTAssertEqual(Stubber.executions(f)[0].result, false)
    XCTAssertEqual(result, false)
  }

  func test_argument3_returnOptionalString_defaultNo() {
    // given
    let f = StubClass().argument3_returnOptionalString_defaultNo
    Stubber.register(f) { _ in "Test" }

    // when
    let result = f("Hello", 10, 20)

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "Hello")
    XCTAssertEqual(Stubber.executions(f)[0].arguments.1, 10)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.2, 20)
    XCTAssertEqual(Stubber.executions(f)[0].result, "Test")
    XCTAssertEqual(result, "Test")
  }

  func test_argument3_returnOptionalString_defaultString() {
    // given
    let f = StubClass().argument3_returnOptionalString_defaultString

    // when
    let result = f("Hello", 10, 20)

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "Hello")
    XCTAssertEqual(Stubber.executions(f)[0].arguments.1, 10)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.2, 20)
    XCTAssertEqual(Stubber.executions(f)[0].result, "default")
    XCTAssertEqual(result, "default")
  }

  func test_argument0_returnOptionalInt_defaultNil() {
    // given
    let f = StubClass().argument0_returnOptionalInt_defaultNil

    // when
    let result = f()

    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertNil(Stubber.executions(f)[0].result)
    XCTAssertNil(result)
  }

  func test_argument2_returnString_defaultNo_throws() {
    // given
    let f = StubClass().argument2_returnString_defaultNo_throws
    Stubber.register(f) { _ in "Stubbed" }

    // when
    let result = try? f("Hello", 30)

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments.0, "Hello")
    XCTAssertEqual(Stubber.executions(f)[0].arguments.1, 30)
    XCTAssertEqual(Stubber.executions(f)[0].result, "Stubbed")
    XCTAssertEqual(result, "Stubbed")
  }

  /// ⚠️ Generic is not supported yet.
  func _test_argument_1_generic() {
    // given
    let f: (String) -> String = StubClass().argument_1_generic
    Stubber.register(f) { _ in "Stubbed" }

    // when
    let result = f("Hi")

    // then
    XCTAssertEqual(Stubber.executions(f).count, 1)
    XCTAssertEqual(Stubber.executions(f)[0].arguments, "Hello")
    XCTAssertEqual(Stubber.executions(f)[0].result, "Stubbed")
    XCTAssertEqual(result, "Stubbed")
  }

  func testThreadSafety() {
    let f = StubClass().sleep
    for _ in 0..<10 {
      DispatchQueue.global().async(execute: f)
    }
    XCTWaiter().wait(for: [XCTestExpectation()], timeout: 1)
  }

  func testClosureCrash() {
    // given
    final class ImageManager {
      func requestImage(for asset: AnyObject, resultHandler: (() -> Void)? = nil) {
        return Stubber.invoke(requestImage, args: (asset, resultHandler), default: Void())
      }
    }

    let imageManager = ImageManager()
    imageManager.requestImage(for: NSObject())

    let executions = Stubber.executions(imageManager.requestImage)

    // crash
    _ = executions.first?.arguments.0
    Stubber.clear()
  }

  func testMatcherArgumentNil() {
    // given
    final class Foo {
      func doSomething(with arg: String?) {
        Stubber.invoke(doSomething(with:), args: arg, default: Void())
      }
    }

    func expect<T>(_ value: T, toHaveCount count: Int, file: StaticString = #file, line: UInt = #line, where: @escaping (T.Iterator.Element) -> Bool) where T: Collection {
      XCTAssertEqual(value.filter(`where`).count, count, file: file, line: line)
    }

    let foo = Foo()
    foo.doSomething(with: nil)

    let executions = Stubber.executions(foo.doSomething(with:))
    expect(executions, toHaveCount: 1) {
      $0.arguments == nil
    }
  }
}
